﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using SharpGL;
using SharpGL.Enumerations;
using SharpGL.Media3D;
using SharpGL.SceneGraph;
using SharpGL.SceneGraph.Assets;
using SharpGL.SceneGraph.Transformations;
using SharpGL.SceneGraph.Effects;
using SharpGL.SceneGraph.Cameras;
using SharpGL.SceneGraph.Lighting;
using SharpGL.SceneGraph.Primitives;
using SharpGL.SceneGraph.Text;

namespace Visualizer3D
{
    public partial class VisualizerSceneControl : SceneControl
    {
        protected LinearTransformationEffect sceneTransformationEffect = new LinearTransformationEffect();
        protected LinearTransformationEffect worldTransformationEffect = new LinearTransformationEffect();
        protected SmartAxies sceneObjectsRoot = new SmartAxies();

        /// <summary>
        /// Initializes 3D objects of scene.
        /// </summary>
        protected void InitializeScene(Scene scene)
        {
            scene.ClearColor = new GLColor(0.5f, 0.5f, 0.5f, 1.0f);

            // Create the look at camera and set it as the current camera.
            /*scene.Camera = new OrthographicCamera()
            {
                Name = "Scene camera",
                Position = new Point3D(0f, 0f, 0f),
                Left = -0.8f,
                Right = 0.8f,
                Top = 0.8f,
                Bottom = -0.8f,
                Near = -10.8f,
                Far = 10.8f
            };*/

            /*scene.CurrentCamera = new PerspectiveCamera()
            {
                Name = "Scene camera",
                Position = new Point3D(0f, 0f, 0f),
                Near = 0.8f,
                Far = 10.8f
            };*/

            /*scene.CurrentCamera = new FrustumCamera()
            {
                Name = "Scene camera",
                Position = new Point3D(0f, 0f, 0f),
                Left = -0.8f,
                Right = 0.8f,
                Top = 0.8f,
                Bottom = -0.8f,
                Near = -0.8f,
                Far = 0.8f
            };*/

            /*scene.Camera = new LookAtCamera()
            {
                Name = "Scene camera",
                Position = new Point3D(0f, 0f, 5.0f),
                Target = new Point3D(0.0f, 0.0f, 0.0f),
                Near = 0.01f,
                Far = 8f,
                UpVector = new Vector3D(0.0f, 1.0f, 0.0f),
                FieldOfView = 30.0f
            };*/

            scene.Camera = new ArcBallCamera()
            {
                Name = "Scene camera",
                Position = new Point3D(0.0f, 0.0f, 5.0f),
                Target = new Point3D(0.0f, 0.0f, 0.0f),
                Near = 0.01f,
                Far = 8f,
                UpVector = new Vector3D(0.0f, 1.0f, 0.0f),
                FieldOfView = 30.0f
            };

            // Create a set of scene attributes.
            OpenGLAttributesEffect sceneAttributes = new OpenGLAttributesEffect()
            {
                Name = "Scene attributes"
            };

            // Specify the scene attributes.
            sceneAttributes.AttributesGroup.EnableAttributes.EnableDepthTest = true;
            sceneAttributes.AttributesGroup.EnableAttributes.EnableNormalize = true;
            sceneAttributes.AttributesGroup.EnableAttributes.EnableLighting = true;
            sceneAttributes.AttributesGroup.EnableAttributes.EnableTexture2D = false;
            //sceneAttributes.EnableAttributes.EnableBlend = true;
            sceneAttributes.AttributesGroup.ColorBufferAttributes.BlendingSourceFactor = BlendingSourceFactor.SourceAlpha;
            sceneAttributes.AttributesGroup.ColorBufferAttributes.BlendingDestinationFactor = BlendingDestinationFactor.OneMinusSourceAlpha;
            sceneAttributes.AttributesGroup.LightingAttributes.TwoSided = true;
            
            // Add effect for all scene
            scene.SceneContainer.Effects.Add(sceneAttributes);

            // Set scene transformation effect params and add it to scene.
            sceneTransformationEffect.LinearTransformation.TransformationOrder = LinearTransformationOrder.TranslateRotateScale;
            scene.SceneContainer.Effects.Add(sceneTransformationEffect);

            // Set world transformation effect params and add it as main scene effect.
            worldTransformationEffect.LinearTransformation.TransformationOrder = LinearTransformationOrder.TranslateRotateScale;
            scene.SceneContainer.Effects.Add(worldTransformationEffect);

            // Create a lights and add them.
            scene.SceneContainer.Children.Add(
                new Light()
                {
                    On = true,
                    Position = new Point3D(3.0f, 10.0f, 3.0f),
                    GLCode = OpenGL.GL_LIGHT0
                });
            scene.SceneContainer.Children.Add(
                new Light()
                {
                    On = true,
                    Position = new Point3D(8.0f, 3.0f, 3.0f),
                    GLCode = OpenGL.GL_LIGHT1
                });
            
            // Add root for all new scene objects
            scene.SceneContainer.Children.Add(sceneObjectsRoot);
            
            // Add some primitives.
            // Add simple axis.
            sceneObjectsRoot.Children.Add(new SharpGL.SceneGraph.Primitives.Axies());                       
            
            // Add cube.
            sceneObjectsRoot.Children.Add(new Cube());

            // Add piilyline.
            var polyline = new SolidPolyline();
            polyline.GenerateNormals = true;
            polyline.Vertices.Add(new Point3D(10.0f, 2.0f, 6.0f));
            polyline.Vertices.Add(new Point3D(1.0f, 3.0f, 7.0f));
            polyline.Vertices.Add(new Point3D(5.0f, 6.0f, 2.0f));
            polyline.Vertices.Add(new Point3D(7.0f, 2.0f, 10.0f));
            polyline.Vertices.Add(new Point3D(4.0f, 8.0f, 2.0f));
            polyline.Vertices.Add(new Point3D(7.0f, 7.0f, 6.0f));
            var polylineEffect = new OpenGLAttributesEffect()
            {
                Name = "Pilyline attributes"
            };
            polylineEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            //polylineEffect.LightingAttributes.EnableColorMaterial = true;
            polylineEffect.AttributesGroup.CurrentAttributes.CurrentColor = new GLColor(0.0f, 1.0f, 0.0f, 1.0f);
            polylineEffect.AttributesGroup.LineAttributes.Width = 0.1f;
            polyline.Effects.Add(polylineEffect);
            sceneObjectsRoot.Children.Add(polyline);

            // Add textured quad.                        
            var texquad = new TexturedQuad(new Point3D(2.0f, 8.0f, 0.0f),
                                           new Point3D(2.0f, 2.0f, 0.0f),
                                           new Point3D(8.0f, 2.0f, 0.0f),
                                           new Point3D(8.0f, 8.0f, 0.0f),
                                           new Size(256, 256));
            texquad.Material = new Material()
            {
                Texture = new Texture()
                {
                    Image = new Bitmap(@"test.bmp")
                }
            };

            var texquadEffect = new OpenGLAttributesEffect()
            {
                Name = "texured quad effect"
            };
            texquadEffect.AttributesGroup.EnableAttributes.EnableTexture2D = true;
            texquadEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = false;
            texquadEffect.AttributesGroup.TextureAttributes.TextureTarget = TextureTarget.Texture2D;
            texquadEffect.AttributesGroup.TextureAttributes.TextureMinFilter = TextureMinFilter.Linear;
            texquadEffect.AttributesGroup.TextureAttributes.TextureMagFilter = TextureMagFilter.Linear;
            texquad.Effects.Add(texquadEffect);
            sceneObjectsRoot.Children.Add(texquad);

            // Add 2D text object.
            var text2DObject = new Text2DObject();
            text2DObject.Text = "Fucking maestro!!!";
            text2DObject.Position = polyline.Vertices[3];
            text2DObject.HorizontalAlignment = SharpGL.SceneGraph.Text.HorizontalAlignment.Center;
            text2DObject.VerticalAlignment = SharpGL.SceneGraph.Text.VerticalAlignment.Top;
            var text2DEffect = new OpenGLAttributesEffect()
            {
                Name = "Text 2D attributes"
            };
            text2DEffect.AttributesGroup.CurrentAttributes.CurrentColor = new GLColor(0.0f, 1.0f, 1.0f, 1.0f);
            text2DEffect.AttributesGroup.CurrentAttributes.CurrentRasterPosition = polyline.Vertices[3];
            text2DObject.Effects.Add(text2DEffect);
            sceneObjectsRoot.Children.Add(text2DObject);

            // Add 3D text object.
            var text3DObject = new Text3DObject();
            text3DObject.Text = "Сраный рашин-пидарашин аяАЯ ЫЫЫ";
            //text3DObject.Font = new Font("Arial", 3.0f, FontStyle.Regular, GraphicsUnit.Point, 204);
            text3DObject.Position = polyline.Vertices[3];
            text3DObject.HorizontalAlignment = SharpGL.SceneGraph.Text.HorizontalAlignment.Center;
            text3DObject.VerticalAlignment = SharpGL.SceneGraph.Text.VerticalAlignment.Center;
            text3DObject.DepthAlignment = SharpGL.SceneGraph.Text.DepthAlignment.Center;
            text3DObject.Extrusion = 1.0f;
            text3DObject.Direction = polyline.Vertices[2] - polyline.Vertices[3];
            text3DObject.Angle = 0.0f;
            var text3DEffect = new OpenGLAttributesEffect()
            {
                Name = "Text 3D attributes"
            };
            text3DEffect.AttributesGroup.EnableAttributes.EnableColorMaterial = true;
            text3DEffect.AttributesGroup.CurrentAttributes.CurrentColor = new GLColor(1.0f, 0.0f, 0.0f, 1.0f);
            text3DObject.Effects.Add(text3DEffect);
            sceneObjectsRoot.Children.Add(text3DObject);
        }

        private void InitializeWorldTransformation(LinearTransformation transformation, BoundingVolume boundingVolume)
        {
            Size3D dim;
            float maxDim;
            float sceneScaleFactor;

            if (boundingVolume.IsEmpty)
                dim = new Size3D(0.0f, 0.0f, 0.0f);
            else
                dim = boundingVolume.Size;

            maxDim = dim.X;
            if (maxDim < dim.Y)
                maxDim = dim.Y;
            if (maxDim < dim.Z)
                maxDim = dim.Z;

            sceneScaleFactor = (0.0f == maxDim ? 1.0f : 1.0f / maxDim);

            transformation.ScaleX = sceneScaleFactor;
            transformation.ScaleY = sceneScaleFactor;
            transformation.ScaleZ = sceneScaleFactor;

            transformation.TranslationVector = (Vector3D)boundingVolume.Centre * -sceneScaleFactor;
        }
        
        private void sceneContainer_SceneTreeChanged(object sender, EventArgs e)
        {
            InitializeWorldTransformation(worldTransformationEffect.LinearTransformation, sceneObjectsRoot.ChildrenBoundingVolume);
            
            Invalidate();
        }

        protected override void OnOpenGLContextInitialized()
        {
            CurrentOpenGLContext.FirstFontGlyph = 0;
            CurrentOpenGLContext.NumberOfFontGlyphs = 1279;

            base.OnOpenGLContextInitialized();

            InitializeWorldTransformation(worldTransformationEffect.LinearTransformation, sceneObjectsRoot.ChildrenBoundingVolume);
        }

        public VisualizerSceneControl()
        {
            InitializeComponent();

            InitializeScene(Scene);
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public LinearTransformationEffect SceneTransformation
        {
            get
            {
                return sceneTransformationEffect;
            }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public override Scene Scene
        {
            get { return base.Scene; }
            set
            {
                sceneObjectsRoot = null;
                foreach (var child in value.SceneContainer.Children)
                {
                    if (child is SmartAxies)
                    {
                        sceneObjectsRoot = (SmartAxies)child;
                        break;
                    }
                }
                if (sceneObjectsRoot != null)
                {
                    // Set scene transformation effect params and add it to scene.
                    sceneTransformationEffect.LinearTransformation.TransformationOrder = LinearTransformationOrder.TranslateRotateScale;
                    value.SceneContainer.Effects.Add(sceneTransformationEffect);

                    // Set world transformation effect params and add it as main scene effect.
                    worldTransformationEffect.LinearTransformation.TransformationOrder = LinearTransformationOrder.TranslateRotateScale;
                    value.SceneContainer.Effects.Add(worldTransformationEffect);

                    base.Scene = value;

                    InitializeWorldTransformation(worldTransformationEffect.LinearTransformation, sceneObjectsRoot.ChildrenBoundingVolume);
                }
                else
                {
                    base.Scene = null;
                }
            }
        }
    }
}
